//
//	MiscStringUNIX.m
//		Written by Don Yacktman Copyright (c) 1993 by Don Yacktman.
//				Version 1.95  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import <misckit/MiscString.h>
#include <pwd.h>

static char theMiscPathSeparator = '/';
static char theMiscExtensionSeparator = '.';

@implementation MiscString(Unix)

// This category includes methods to support various UNIX features,
// such as file/path name parsing and encryption of passwords.

+ setPathSeparator:(char)c { theMiscPathSeparator = c; return self; }
+ setExtensionSeparator:(char)c { theMiscExtensionSeparator = c; return self; }
+ (char)extensionSeparator { return theMiscExtensionSeparator; }
+ (char)pathSeparator { return theMiscPathSeparator; }

- encrypt:salt
{	// encrypt as a UNIX password using the MiscString "salt" as the salt...
	// see crypt(3) for more info
	// The cast prevents a warning:  -stringValue returns a const char *.
	// Assuming crypt() doesn't change the salt, the cast is OK.
	char *strv;
	if ([salt respondsTo:@selector(stringValue)])
		strv = (char *)[salt stringValue];
	else return nil;
	if (!(buffer && strv)) return nil;
	if (!length || !strlen(strv)) return nil;
	return [[[self class] alloc] initString:crypt(buffer, strv)];
}

- fileNameFromZone:(NXZone *)zone
{
	return [self extractPart:MISC_STRING_LAST
				useAsDelimiter:theMiscPathSeparator
				caseSensitive:YES fromZone:zone];
}

- fileName
{
	return [self fileNameFromZone:[self zone]];
}

- pathNameFromZone:(NXZone *)zone
{
	id temp = [self left:[self rspotOf:theMiscPathSeparator] fromZone:zone];
	if (temp) return temp;
	return [[self class] new];
}

- pathName
{
	return [self pathNameFromZone:[self zone]];
}

- fileExtensionFromZone:(NXZone *)zone
{
	id temp = [self fileNameFromZone:[self zone]];
	id extension = [temp extractPart:MISC_STRING_LAST
				useAsDelimiter:theMiscExtensionSeparator
				caseSensitive:YES fromZone:zone];
	if ([temp isEqual:extension] ||
			(0 == [temp rspotOf:theMiscExtensionSeparator
			occurrenceNum:0 caseSensitive:YES]))
		[extension setStringValue:""];
	[temp free];
	return extension;
}

- fileExtension
{
	return [self fileExtensionFromZone:[self zone]];
}

- fileBasenameFromZone:(NXZone *)zone
{
	id base, temp = [self fileNameFromZone:[self zone]];
	int right = [temp rspotOf:theMiscExtensionSeparator
			occurrenceNum:0 caseSensitive:YES] - 1;
	if (right < 0) right = length - 1;
	base = [temp midFrom:0 to:right fromZone:zone];
	[temp free];
	return (base ? base : [[self class] new]);
}

- fileBasename
{
	return [self fileBasenameFromZone:[self zone]];
}

- addExtensionIfNeeded:(const char *)aString
{
	id	ourExtension;
	
	if (!aString) return nil;  // maybe we should _strip_ extensions here
	
	ourExtension = [self fileExtension];

	if ( [ourExtension length] == 0 ||
		[ourExtension cmp:aString] != 0 ) {
		if ([self charAt:(length - 1)] != theMiscExtensionSeparator)
			[self addChar:theMiscExtensionSeparator];
		[self cat:aString];
	}	
	[ourExtension free];
	return self;
}

// June 1995: Carl Lindberg
// Rewrote -replaceHomeWithTilde and -replaceTildeWithHome because they
//   a) had bad problems when user was root
//   b) thought all users home dirs were in the same directory, and
//   c) had problems when one username was a prefix of another
//       (e.g., "tom" and "tomsmith").
// It was rather bad code, in retrospect. *Sigh*  I hope I thought
// of everything this time.

- replaceHomeWithTilde
{
	id homestr = [[self class] newWithString:NXHomeDirectory()];
	char rootdir[2] = {theMiscPathSeparator,0};

    // Make sure user isn't root, and also be careful not to
    // "/Users/tom" is not taken to be a match when the current
    // string is "/Users/tomsmith/Apps" (that's what the third
    // condition checks)
    if (![self compareTo:homestr n:[homestr length]] &&
       ([homestr cmp:rootdir]) &&
       (([self charAt:[homestr length]] == theMiscPathSeparator) ||
        ([homestr length] == [self length])))
    {
	    [self replaceFrom:0 length:[homestr length] withChar:'~'];
	}
	else {   // see if we can match a user's home dir, and if so
	         // translate to "~username/subdir"
	  struct passwd *userEntry;
	  
	  setpwent();
	  while (userEntry = getpwent()) {
	    [homestr setStringValue:userEntry->pw_dir];
	    if (([homestr length]) &&
            ([homestr cmp:rootdir])  &&
            (![self compareTo:homestr n:[homestr length]]) &&
            (([self charAt:[homestr length]] == theMiscPathSeparator) ||
             ([homestr length] == [self length])))
		{
		  [self replaceFrom:0 length:[homestr length] with:"~"];
		  [self insert:userEntry->pw_name at:1];
		  break;
		}
      }
	  endpwent();
	}
	[homestr free];
	return self;
}

- replaceTildeWithHome
{
	id home;
	char rootdir[2] = {theMiscPathSeparator,0};

	if (!buffer || (buffer[0] != '~')) return self;

    home = [[self class] newWithString:NXHomeDirectory()];

	if (length == 1) {                 //string is just "~"
	  [self takeStringValueFrom:home];
	}
	else if (buffer[1] == theMiscPathSeparator) { //starts with "~/"
      if (![home cmp:rootdir])         // root--take out "~"
        [self removeFrom:0 to:0];
      else
	    [self replaceFrom:0 length:1 withString:home];
	}
	else {        // a "~username" variety; check if username exists
	  int spot = [self spotOf:theMiscPathSeparator];
	  id username;
	  struct passwd *userEntry;
	  if (spot == -1) spot = length;

	  username = [self midFrom:1 to:spot-1];
      userEntry = getpwnam([username stringValue]);
	  if (userEntry && userEntry->pw_dir) {
	    [self replaceFrom:0 to:spot-1 with:userEntry->pw_dir];
	  }
	  [username free];
	}
	[home free];
	return self;
}

- (BOOL)isRelativePath
// Returns YES if the path is non-empty and does NOT begin with pathSeparator.
{
	id tempPath = [self pathName];
	if ([tempPath length] && ([tempPath charAt:0] != theMiscPathSeparator)) {
		[tempPath free];
		return YES;
	}
	[tempPath free];
	return NO;
}

- (BOOL)isAbsolutePath
// Returns YES if the path is non-empty and DOES begin with pathSeparator.
{
	id tempPath = [self pathName];
	if ([tempPath length] && ([tempPath charAt:0] == theMiscPathSeparator)) {
		[tempPath free];
		return YES;
	}
	[tempPath free];
	return NO;
}

- (BOOL)doesExistInFileSystem
// Returns YES if the a file exists at our path, and is visible to the user 
// id of the process.  A NO can mean any number of things, but a YES 
// definitely indicates that the file is there and visible.
{
	struct stat statBuff;
	if (!buffer) return NO;
	if (stat([self stringValue], &statBuff) == -1) return NO;
	return YES;
}

- (BOOL)isFileOfType:(MiscFileType)fileType
// Returns YES if the named file exists, and is visible to the user id of
// the process and it is a file of type fileType.  A NO can mean any number
// of things,  but a YES definitely indicates that the file is there and
// visible and of the fileType type.  
{
	struct stat statBuff;
	if (!buffer) return NO;
	if (stat([self stringValue], &statBuff) == -1) return NO;
	return ((statBuff.st_mode & fileType) == fileType);
}

- pathComponentAt:(int)index
{
	if ((index < 0) || (index >= [self numberOfPathComponents]))
		return [[self class] newWithString:""];
	return [self extractPart:([self isAbsolutePath] ? (index + 1) : index)
				useAsDelimiter:theMiscPathSeparator
				caseSensitive:YES fromZone:[self zone]];
}

- (int)numberOfPathComponents
{
	return ([self numOfChar:theMiscPathSeparator] -
			([self isAbsolutePath] ? 1 : 0));	
}

/*
 * Modified [-setDirectory:file:] and [-initDirectory:file:] to use the
 * official misc path separator, rather than a hard-coded '/'. BJM 7/9/94
 */
- setDirectory:(const char *)dir file:(const char *)file
{
	return [self setFromFormat:"%s%c%s", dir, theMiscPathSeparator, file];
}

- initDirectory:(const char *)dir file:(const char *)file
{
	return [self initFromFormat:"%s%c%s", dir, theMiscPathSeparator, file];
}

- (int)system
{
	return system([self stringValue]);
}

- loadFromFile:(const char *)fileName
{
	int theFile;
	struct stat fileInfo;

	if (!fileName || stat(fileName, &fileInfo) ||
			!(theFile = open(fileName, O_RDONLY))) { 
		return nil;
	}
	if (fileInfo.st_size) {                     
		[self allocateBuffer:fileInfo.st_size]; // make sure we're big enough
		if (fileInfo.st_size != read(theFile, buffer, fileInfo.st_size)) {
			close(theFile);
			return nil;
		}
	}
	close(theFile);
	[self recalcLength];
	return self;
}

@end
